/*
@2024-11-20: 将PL_isMaster功能集成到
@2024-10-23: 基于单芯片相机的socket服务端程序多线程版适配修改后用于2X2拼接相机。该服务端程序运行于4个zynq
             ps端，作为后台程序

*/

#include "MephistoCAM.h"

#define PORT            9527
#define LISTEN_QUEUE    16
#define BUFFER_SIZE     1024

#ifndef PTHREAD_MUTEX_NORMAL
    #define PTHREAD_MUTEX_NORMAL PTHREAD_MUTEX_TIMED_NP
#endif

#if defined(_ENABLE_FASTER_READOUT_)
    #define _FAST_READ_ 1
#else
    #define _FAST_READ_ 0
#endif

time_t arm_sys_time = 0;
struct timeval start;
struct timeval end;

char timestamp0[26]; // 'op' + 23+'\n' = 26
char timestamp1[26]; // 'cl' + 23+'\n' = 26
char timestamp[51];

char myName[64];        // 保存每个CCD的名称Blue1, Blue2, Blue3, Blue4
uint32_t isMaster = 0;  // 默认全部设为0，即slave模式
char binFilename[512];  // 保存输出bin文件的名字

////////////////////////////////////////////////////////////////////////////////////
// 备份CCD驱动时序参数
// backup vtrans parameters
int vtrans_num_bak; //      = mcfg->config_ccd_drive->vtrans_num;
int imgCLK1_cnt_min_bak;    // = mcfg->config_ccd_drive->imgCLK1_cnt_min;
int imgCLK1_cnt_max_bak;    // = mcfg->config_ccd_drive->imgCLK1_cnt_max;
int imgCLK2_cnt_min_bak;    // = mcfg->config_ccd_drive->imgCLK2_cnt_min;
int imgCLK2_cnt_max_bak;    // = mcfg->config_ccd_drive->imgCLK2_cnt_max;
int imgCLK3_cnt_min_bak;    // = mcfg->config_ccd_drive->imgCLK3_cnt_min;
int imgCLK3_cnt_max_bak;    // = mcfg->config_ccd_drive->imgCLK3_cnt_max;
int imgCLK4_cnt_min_bak;    // = mcfg->config_ccd_drive->imgCLK4_cnt_min;
int imgCLK4_cnt_max_bak;    // = mcfg->config_ccd_drive->imgCLK4_cnt_max;
int imgCLKx_cnt_MAX_bak;    // = mcfg->config_ccd_drive->imgCLKx_cnt_MAX;
int vtrans_state_cnt_max_bak;   // = mcfg->config_ccd_drive->vtrans_state_cnt_max;

// backup htrans parameters
int htrans_num_bak; // = mcfg->config_ccd_drive->htrans_num;
///////////////////////////////////////////////////////////////////////////////////

uint32_t cStatus = 0;   // 记录当前的工作状态，有且仅有两种可能得状态：
                        // 0: 表示空闲，没有曝光或者读出
                        // 1: 表示接收到ICS的拍摄指令了，正在执行中

typedef struct _SockInfo {
    struct sockaddr_in addr;
    int fd;
    MephistoCAM_Config *mcfg;
}SockInfo;

SockInfo infos[LISTEN_QUEUE];

SPI_CONFIG *spi_config_daisy;// = (SPI_CONFIG*)malloc(sizeof(SPI_CONFIG));

pthread_mutex_t abort_mutex;

int extractCommand( const char sock_msg[1024], char *operator, char *operand );

int ccd_readout_fk( uint32_t *gpio_reg_ctrl, 
                 uint32_t *gpio_reg_status, 
                 uint32_t *gpio_reg_pl_ps_ddr_cnt, 
                 uint32_t n_row,
                 uint32_t n_col,
                 uint32_t n_bytes,    // 2 for 16bits unsigned; 4 for 32bits float
                 uint32_t fast_read_opt,
                 uint32_t IamMaster,
                 char *filename );
int ccd_readout_fpXX( uint32_t *gpio_reg_ctrl, 
                 uint32_t *gpio_reg_status, 
                 uint32_t *gpio_reg_pl_ps_ddr_cnt, 
                 uint32_t n_row,
                 uint32_t n_col,
                 uint32_t n_bytes,    // 2 for 16bits unsigned; 4 for 32bits float
                 uint32_t fast_read_opt,
                 uint32_t IamMaster,
                 char *filename );

void *serverForClient(void * arg);
void abortExposure(void *arg);

static int global_abort_exposure = 0;

int main( int argc, char *argv[] ) {

    if( argc < 2 ){
        printf("\n*********************************************\n");
        printf("usage: %s myName\n", argv[0]);
        printf("*********************************************\n");
        return 0;
    }
    
    sprintf(myName, "%s", argv[1]);
    fprintf(stdout, "# My Name is : %s\n", myName);
    

    // pthread_mutex_init(&abort_mutex, PTHREAD_MUTEX_TIMED_NP);
    pthread_mutex_init(&abort_mutex, PTHREAD_MUTEX_NORMAL);

    //=================================================
    //  导入所有配置参数
    //=================================================
    char config_dir[1024];      // 保存参数配置文件所在的目录(相对路径)
    char config_dir_tmp[512];


    FILE *fp_wkdir = fopen(".WKDIR", "r");
    fscanf(fp_wkdir, "%s", config_dir_tmp);
    printf("&&& %s\n", config_dir_tmp);
    fclose(fp_wkdir);

    sprintf(config_dir, "%s_%s", config_dir_tmp, myName);

    printf("### %s\n", config_dir);
    // exit(0);

    MephistoCAM_Config MCAM_Config;
    init_MephistoCAM_Config( &MCAM_Config, config_dir );


    // backup vtrans parameters
    vtrans_num_bak          = MCAM_Config.config_ccd_drive->vtrans_num;
    imgCLK1_cnt_min_bak     = MCAM_Config.config_ccd_drive->imgCLK1_cnt_min;
    imgCLK1_cnt_max_bak     = MCAM_Config.config_ccd_drive->imgCLK1_cnt_max;
    imgCLK2_cnt_min_bak     = MCAM_Config.config_ccd_drive->imgCLK2_cnt_min;
    imgCLK2_cnt_max_bak     = MCAM_Config.config_ccd_drive->imgCLK2_cnt_max;
    imgCLK3_cnt_min_bak     = MCAM_Config.config_ccd_drive->imgCLK3_cnt_min;
    imgCLK3_cnt_max_bak     = MCAM_Config.config_ccd_drive->imgCLK3_cnt_max;
    imgCLK4_cnt_min_bak     = MCAM_Config.config_ccd_drive->imgCLK4_cnt_min;
    imgCLK4_cnt_max_bak     = MCAM_Config.config_ccd_drive->imgCLK4_cnt_max;
    imgCLKx_cnt_MAX_bak     = MCAM_Config.config_ccd_drive->imgCLKx_cnt_MAX;
    vtrans_state_cnt_max_bak= MCAM_Config.config_ccd_drive->vtrans_state_cnt_max;
    
    // backup htrans parameters
    htrans_num_bak          = MCAM_Config.config_ccd_drive->htrans_num;

    //=========================================================================
    // GPIO 
    // uint32_t *gpio_flt_ctrl;        // ADDR_Float_Point_CTRL
    // uint32_t *gpio_flt_data;        // ADDR_Float_Point_DATA
    // uint32_t *gpio_in;              // ADDR_GPIO_IN
    // uint32_t *gpio_pl_ps;           // ADDR_PL_PS_DDR_CNT
    // uint32_t *gpio_out;             // ADDR_GPIO_OUT
    // uint32_t *gpio_out2;            // ADDR_GPIO_OUT
    // uint32_t *gpio_spi_ps_ctrl;     // ADDR_GPIO_SPI_PS_CTRL
    // uint32_t *gpio_spi_ps_data;     // ADDR_GPIO_SPI_PS_DATA


#if defined(_DEBUG_SOCKET_SERVER_)
    MCAM_Config.gpio_flt_ctrl   = NULL;
    MCAM_Config.gpio_flt_data   = NULL;
    MCAM_Config.gpio_in         = NULL;
    MCAM_Config.gpio_pl_ps      = NULL;
    MCAM_Config.gpio_out        = NULL;
    MCAM_Config.gpio_out2       = NULL;
    MCAM_Config.gpio_spi_ps_ctrl= NULL;
    MCAM_Config.gpio_spi_ps_data= NULL;
#else
    int fd_gpio = open("/dev/mem", O_RDWR | O_SYNC);

    uint32_t *gpio_flt_ctrl     = mmap(NULL, 10, PROT_READ | PROT_WRITE, MAP_SHARED, fd_gpio, ADDR_Float_Point_CTRL);
    uint32_t *gpio_flt_data     = gpio_flt_ctrl + 2;

    uint32_t *gpio_in           = mmap(NULL, 10, PROT_READ | PROT_WRITE, MAP_SHARED, fd_gpio, ADDR_GPIO_IN);
    uint32_t *gpio_pl_ps        = gpio_in + 2;

    uint32_t *gpio_out          = mmap(NULL, 10, PROT_READ | PROT_WRITE, MAP_SHARED, fd_gpio, ADDR_GPIO_OUT);
    uint32_t *gpio_out2         = gpio_out + 2;

    uint32_t *gpio_spi_ps_ctrl  = mmap(NULL, 10, PROT_READ | PROT_WRITE, MAP_SHARED, fd_gpio, ADDR_GPIO_SPI_PS_CTRL);
    uint32_t *gpio_spi_ps_data  = gpio_spi_ps_ctrl + 2;

    MCAM_Config.gpio_flt_ctrl   = gpio_flt_ctrl;
    MCAM_Config.gpio_flt_data   = gpio_flt_data;
    MCAM_Config.gpio_in         = gpio_in;
    MCAM_Config.gpio_pl_ps      = gpio_pl_ps;
    MCAM_Config.gpio_out        = gpio_out;
    MCAM_Config.gpio_out2       = gpio_out2;
    MCAM_Config.gpio_spi_ps_ctrl= gpio_spi_ps_ctrl;
    MCAM_Config.gpio_spi_ps_data= gpio_spi_ps_data;
#endif
    //=================================================
    //      SOCKET
    //=================================================

    // initialize client Info
    int sock_max_size = sizeof(infos) / sizeof(infos[0]);
    int i;
    for( i=0; i<sock_max_size; i++ ){
        bzero(&infos[i], sizeof(infos[i]));
        infos[i].fd     = -1;
        infos[i].mcfg   = NULL;
    }

    //初始化socket
    int sock_fd = socket(AF_INET,SOCK_STREAM,0);
    int on = 1;                                         // Enable address reuse
    setsockopt( sock_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on) );

    struct sockaddr_in server_sockaddr;                 // socket service
    struct sockaddr_in client_addr;                     // socket client
    socklen_t sin_size = sizeof(struct sockaddr_in);

    memset( &server_sockaddr, 0, sizeof(server_sockaddr));
    bzero( &client_addr, sizeof(client_addr) );

    server_sockaddr.sin_family      = AF_INET;
    server_sockaddr.sin_port        = htons(PORT);
    server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);

    ///bind成功返回0出错返回-1
    if( bind(sock_fd, (struct sockaddr *)&server_sockaddr, sizeof(server_sockaddr)) == -1 )
    {
        fprintf(stderr, "[Mephisto_RED_CAM] Failed to bind socket, quit.\n");
        exit(-1);
    }

    ///listen成功返回0出错返回-1
    if( listen( sock_fd, LISTEN_QUEUE ) < -1 )
    {
        fprintf(stderr, "[Mephisto_RED_CAM]  listen function failed.\n");
        exit(-1);
    }

    printf("[Mephisto_RED_CAM] start socket listening Port: %d ...\n", PORT);

    while(1) {

        SockInfo *pinfo = NULL;

        for( i=0; i<sock_max_size; i++ ){
            if( infos[i].fd == -1 ){
                pinfo       = &infos[i];
                break;
            }
        }

        int cfd = accept( sock_fd, (struct sockaddr *)&client_addr, &sin_size ); //accpet
        
        if( -1 == cfd ) {
            perror("[Mephisto_RED_CAM] socket accept failed! \n");
            fprintf(stdout, "[Mephisto_RED_CAM] continue trying to accept socket connections ...\n");
            continue;
        }

        pinfo->fd   = cfd;
        pinfo->mcfg = &MCAM_Config;

        if( pinfo != NULL ){
            fprintf(stdout, "\n[Mephisto_RED_CAM: main thread] --> SockInfo infos[%2d] is activated\n", i);
            fprintf(stdout, "[Mephisto_RED_CAM: main thread] --> socket fd: %d\n\n", cfd);
        }

        // create new thread
        // if( cStatus == 0 ){
            pthread_t tid;
            pthread_create(&tid, NULL, serverForClient, pinfo);
            pthread_detach(tid);
        // }

        usleep(1000);
    }

    close(sock_fd);

    return 0;
}

/*
@2024-01-22: update vertical transfer drive parameters for "refresh" period, to reduce
             the refresh time.
*/
void *serverForClient(void *arg) {

    SockInfo           *pinfo  = (SockInfo*)arg;
    MephistoCAM_Config *mcfg   = pinfo->mcfg;
    
    char recvbuf[BUFFER_SIZE];
    char sendbuf[BUFFER_SIZE] = "recv successfully.\n";
    char return_msg[1040]= "";
    char    operator[128]   = "";
    char    operand[256]    = "";
    int     operand_i       = 0;
    double  operand_f       = 0.0;

    int new_fd = pinfo->fd;

    while(1) {
        
        bzero(recvbuf, sizeof(recvbuf));
        bzero(sendbuf, sizeof(sendbuf));

        // print client IP/port
        // char ip[32];
        // fprintf(stdout, "Client IP: %s, port: %d ==> ",
        //     inet_ntop(AF_INET, &pinfo->addr.sin_addr.s_addr, ip, sizeof(ip)),
        //     ntohs(pinfo->addr.sin_port));

        socklen_t len = recv(new_fd, recvbuf, sizeof(recvbuf), 0);

        sprintf(return_msg, "%s_ok", recvbuf);

        if( len > 0 ){

            fprintf(stdout, "[command from ICS] %s\n", recvbuf);

            extractCommand( recvbuf, operator, operand );

            if( strcmp(recvbuf, "readout") == 0 ){

#if defined(_DEBUG_SOCKET_SERVER_)
                ccd_readout_fk( mcfg->gpio_out, 
                                mcfg->gpio_in, 
                                mcfg->gpio_pl_ps,
                                mcfg->n_row,
                                mcfg->n_col,
                                mcfg->n_bytes,    // 2 for 16bits unsigned; 4 for 32bits float
                                _FAST_READ_,
                                isMaster,
                                binFilename );
#else
                sprintf(binFilename, "tmp/%s_tmp.bin", myName);
                ccd_readout_fpXX( mcfg->gpio_out, 
                                mcfg->gpio_in, 
                                mcfg->gpio_pl_ps,
                                mcfg->n_row,
                                mcfg->n_col,
                                mcfg->n_bytes,    // 2 for 16bits unsigned; 4 for 32bits float
                                _FAST_READ_,
                                isMaster,
                                binFilename );

                

/*                printf(">>>>>> doing fake readout <<<<<<<\n");*/
#endif

                strcpy(sendbuf, return_msg);
                send(new_fd, sendbuf, strlen(sendbuf), 0);
            } else if( strcmp(recvbuf, "setbfname") == 0 ){
                sprintf(binFilename, "%s", operand);
                
                strcpy(sendbuf, return_msg);
                send(new_fd, sendbuf, strlen(sendbuf), 0);
            } else if( strcmp(recvbuf, "set_as_master") == 0 ){
                isMaster = 1;

                setbit(*(mcfg->gpio_out), BIT_IS_MASTER);
                printf("> set as master, isMaster = %d\n", isMaster);

                strcpy(sendbuf, return_msg);
                send(new_fd, sendbuf, strlen(sendbuf), 0);
                printf("> send %s to Camera finder ...\n", myName);
            } else if( strcmp(recvbuf, "set_as_slave") == 0 ){
                isMaster = 0;
                
                clrbit(*(mcfg->gpio_out), BIT_IS_MASTER);
                printf("> set as slave, isMaster = %d\n", isMaster);
                
                strcpy(sendbuf, return_msg);
                send(new_fd, sendbuf, strlen(sendbuf), 0);
                printf("> send %s to Camera finder ...\n", myName);
            } else if( strcmp(recvbuf, "whoareyou") == 0 ){
                printf("> received whoareyou cmd ...\n");
                
                memset(sendbuf,0,sizeof(sendbuf));
                strcpy(sendbuf, myName);
                send(new_fd, sendbuf, strlen(sendbuf), 0);

                printf("> send %s to Camera finder ...\n", myName);
            } else if( strcmp(operator, "set_host_dir") == 0 ){
                printf("> prentending to mount dir: %s\n", operand);
                char cmd[1024];
                sprintf(cmd, "mount -t nfs -o tcp,nolock %s", operand);
                fprintf(stdout, "cmd: %s\n", cmd);
                system(cmd);
                
                strcpy(sendbuf, return_msg);
                send(new_fd, sendbuf, strlen(sendbuf), 0);
                printf("> mount host dir done\n");
            } else if( strcmp(operator, "exit") == 0 ){
                // char stmp[256];
                // sprintf(stmp, "close connection from socket_fd: %d\n", new_fd);
                // send(new_fd, stmp, sizeof(stmp), 0);
                send(new_fd, "connection close.\n", sizeof("connection close.\n"), 0);

                break;
            } else {
                printf("\n\t*******************************\n");
                printf("\t***** unresolved command ******\n");
                printf("\t*******************************\n");
                break;
            }

            memset(recvbuf,0,sizeof(recvbuf));
            memset(operator, 0, sizeof(operator));
            memset(operand, 0, sizeof(operand));
        }

        usleep(1000);   // 1ms sleep
    }

    close(pinfo->fd);
    pinfo->fd   = -1;
    pinfo->mcfg = NULL;

    close(new_fd);

    fprintf(stdout,"##> thread: %lu closed.\n", pthread_self());
    pthread_exit(0);

    return NULL;
}


void abortExposure(void *arg){
    pthread_t ctid = pthread_self();

    pthread_mutex_lock(&abort_mutex);
    global_abort_exposure = 1;
    pthread_mutex_unlock(&abort_mutex);

    fprintf(stdout, "[THREAD: %lu] send 'ABORT EXPOSURE' signal!\n", ctid);
}

int extractCommand( const char sock_msg[1024], char *operator, char *operand ){
    char local_str[1024];
    strcpy(local_str, sock_msg);
    
    char *p;
    sprintf(operator, "%s", strtok(local_str, "|"));
    // fprintf(stdout, "##> operator: --%s--\n", operator);
    
    p = strtok(NULL, "|");

    if( p != NULL ){
        sprintf(operand, "%s\n", p);
        // fprintf(stdout, "##> operand:  --%s--\n", operand);
    }

    return 0;
}

/*
ccd_readout_fp的后缀fp表示写日志的文件描述符
@20240818: update the data storage startegy to reduce total readout time
*/
int ccd_readout_fpXX( uint32_t *gpio_reg_ctrl, 
                 uint32_t *gpio_reg_status, 
                 uint32_t *gpio_reg_pl_ps_ddr_cnt, 
                 uint32_t n_row,
                 uint32_t n_col,
                 uint32_t n_bytes,    // 2 for 16bits unsigned; 4 for 32bits float
                 uint32_t fast_read_opt,
                 uint32_t IamMaster,
                 char *filename ){

    uint32_t base_addr = 0x20000000;    // 512MByte 特殊地址值！！！！
    int fd = open("/dev/mem", O_RDWR | O_SYNC);

#if defined(_ENABLE_FASTER_READOUT_)
//  ===========================================================================================
//  @20240818: New and faster data storage strategy

    uint32_t mem_size_in_Bytes = n_col*16*n_bytes;   // the actual number of Bytes of a row
    uint32_t *mem_ptr = mmap(NULL, n_row*n_col*16*n_bytes, PROT_READ | PROT_WRITE, MAP_SHARED, fd, base_addr);
    uint32_t *ptr = mem_ptr;

    FILE *fb = NULL;
    fb = fopen(filename, "wba");   // append mode

    struct timeval start;
    struct timeval end;
    gettimeofday(&start, NULL);
    
    if( IamMaster == 1 ){
        printf("###> I am the Master, I will send the read readout command to the Camera!\n");
        Trig_Readout( gpio_reg_ctrl );      // trigger readout
    } else {
        // printf("###> I'm just a poor slave, I will trying collect data from DDR after 1ms\n");
        usleep(1000);
    }

    uint32_t row_cnt = 0;
    while(1){
        
        if( getbit(*gpio_reg_status, BIT_DDR_DATA_RDY) ){
            fwrite(ptr, sizeof(unsigned char), mem_size_in_Bytes, fb);
            ptr += n_col*n_bytes*4;
            row_cnt++;
        }

        if( row_cnt >= n_row )
            break;
    }

    gettimeofday(&end, NULL);
    fprintf(stdout, "FPGA readout used time: %10.6f s\n", time_diff(&start, &end));

    if( fb != NULL )
        fclose(fb);

    char bin_ok_flag[256];
    sprintf(bin_ok_flag, "%s_ok", filename);
    FILE *fp_ok = fopen(bin_ok_flag, "w");
    fprintf(fp_ok, "1");
    fclose(fp_ok);

    munmap(mem_ptr, n_row*n_col*16*n_bytes);

#else
//  ===========================================================================================
//  **** Old and slow data storage strategy ****
    uint32_t mem_size_in_Bytes_X = n_col*n_row*16*n_bytes;   // the actual size
    uint32_t *mem_ptr = mmap(NULL, mem_size_in_Bytes_X, PROT_READ | PROT_WRITE, MAP_SHARED, fd, base_addr);

    setbit( *gpio_reg_ctrl, BIT_PS_DATA_SAVED );
    // Trig_Readout( gpio_reg_ctrl );

    if( IamMaster == 1 ){
        printf("===> I am the Master, I will send the read readout command to the Camera!\n");
        Trig_Readout( gpio_reg_ctrl );      // trigger readout
    } else {
        // printf("===> I'm just a poor slave, I will trying collect data from DDR after 1ms\n");
        usleep(1000);
    }
    
    time_t t_start, t_end;
    time(&t_start);
    uint32_t rd_is_done = getbit( *gpio_reg_status, BIT_READOUT_DONE );
    printf("[CCD_READOUT] --> start\n");
    
    while( rd_is_done != 1 ){   // check whether readout is over
        rd_is_done = getbit(*gpio_reg_status, BIT_READOUT_DONE);
        if( rd_is_done == 1 ){
            printf("[CCD_READOUT] --> finish!\n");
            break;
        }
        usleep(1000);
    }

    FILE *fb = NULL;
    fb = fopen(filename, "wb");
    printf("[CCD_READOUT] --> start transferring data from DDR to disk ...\n");
    fwrite((void *)mem_ptr, sizeof(unsigned char), mem_size_in_Bytes_X, fb);


    time(&t_end);
    printf("[CCD_READOUT] --> total time used %ld seconds.\n", t_end - t_start);

/*    write_log(fp, "finished transferring data.");*/

    if( fb != NULL )
        fclose(fb);
    
    char bin_ok_flag[256];
    sprintf(bin_ok_flag, "%s_ok", filename);
    FILE *fp_ok = fopen(bin_ok_flag, "w");
    fprintf(fp_ok, "1");
    fclose(fp_ok);

    memset(mem_ptr, rand()%100, n_col*n_row*16);

    munmap(mem_ptr, mem_size_in_Bytes_X);
#endif

    close(fd);
    DropCaches(3);    //  清空缓存

    return 0;
}

int ccd_readout_fk( uint32_t *gpio_reg_ctrl, 
                    uint32_t *gpio_reg_status, 
                    uint32_t *gpio_reg_pl_ps_ddr_cnt, 
                    uint32_t n_row,
                    uint32_t n_col,
                    uint32_t n_bytes,    // 2 for 16bits unsigned; 4 for 32bits float
                    uint32_t fast_read_opt,
                    uint32_t IamMaster,
                    char *filename ){
    
    if( IamMaster == 1 ){
        printf("> I am the Master, I will send the read readout command to the Camera!\n");
    } else {
        printf("> I'm just a poor slave, I will trying collect data from DDR after 1ms\n");
        usleep(1000);
    }

    return 0;
}
